Passed
Push — main ( b7799b...6c7a2f )
by Bjarn
03:09 queued 01:45
created

InstallController   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 335
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 246
dl 0
loc 335
rs 10
c 0
b 0
f 0
wmc 19

10 Functions

Rating   Name   Duplication   Size   Complexity  
A installTools 0 23 2
A installNginx 0 21 2
A installMailhog 0 21 2
A installDatabase 0 21 2
A installOptionalServices 0 35 2
A configureJale 0 10 1
A installDnsMasq 0 21 2
A execute 0 16 1
A install 0 36 2
A installPhpFpm 0 43 3
1
import * as fs from 'fs'
2
import inquirer, {Answers} from 'inquirer'
3
import {white} from 'kleur/colors'
4
import {Listr, ListrTask} from 'listr2'
5
import {Config, Database} from '../models/config'
6
import Dnsmasq from '../services/dnsmasq'
7
import Mailhog from '../services/mailhog'
8
import Nginx from '../services/nginx'
9
import {fallbackIndex} from '../templates/fallbackServer'
10
import {clearConsole} from '../utils/console'
11
import {getDatabaseByName} from '../utils/database'
12
import {ensureDirectoryExists} from '../utils/filesystem'
13
import {getOptionalServiceByname} from '../utils/optionalService'
14
import {client} from '../utils/os'
15
import {getPhpFpmByName} from '../utils/phpFpm'
16
import {ensureHomeDirExists, jaleConfigPath, jaleFallbackServer, jaleLogsPath} from '../utils/jale'
17
import {requireSudo} from '../utils/sudo'
18
import {getToolByName} from '../utils/tools'
19
20
class InstallController {
21
22
    private readonly questions = [
23
        {
24
            type: 'input',
25
            name: 'domain',
26
            message: 'Enter a domain',
27
            default: 'test',
28
            validate: (input: string) => {
29
                return input !== ''
30
            }
31
        },
32
        {
33
            type: 'checkbox',
34
            name: 'phpVersions',
35
            message: 'Choose one or more PHP versions',
36
            choices: ['[email protected]', '[email protected]', '[email protected]', '[email protected]', '[email protected]'],
37
            validate: (input: string[]) => {
38
                return input.length >= 1
39
            }
40
        },
41
        {
42
            type: 'list',
43
            name: 'database',
44
            message: 'Choose a database',
45
            choices: ['[email protected]', '[email protected]', 'mariadb'],
46
            validate: (input: string[]) => {
47
                return input.length >= 1
48
            }
49
        },
50
        {
51
            type: 'checkbox',
52
            name: 'optionalServices',
53
            message: 'Optional services',
54
            choices: ['redis', 'elasticsearch']
55
        },
56
        {
57
            type: 'checkbox',
58
            name: 'apps',
59
            message: 'Tools and apps',
60
            choices: ['wp-cli', 'magerun', 'magerun2', 'drush']
61
        }
62
    ]
63
64
    /**
65
     * Execute the installation process.
66
     */
67
    execute = async (): Promise<boolean> => {
68
        clearConsole()
69
        console.log(white('✨ Thanks for using Jale! Let\'s get you started quickly.\n'))
70
71
        await requireSudo()
72
73
        inquirer
74
            .prompt(this.questions)
75
            .then(answers => {
76
                this.install(answers)
77
            })
78
            .catch(error => {
79
                console.log('Something went wrong. However, this version is just a proof of concept and the error handling sucks. Sorry, again.')
80
            })
81
82
        return true
83
    }
84
85
    /**
86
     * Start the installation of Jale.
87
     *
88
     * @param answers
89
     * @private
90
     */
91
    private async install(answers: any) {
92
        await ensureHomeDirExists()
93
        await ensureDirectoryExists(jaleLogsPath)
94
95
        await fs.writeFileSync(jaleFallbackServer, fallbackIndex)
96
97
        const tasks = new Listr([
98
            this.configureJale(answers),
99
            this.installDnsMasq(),
100
            this.installNginx(),
101
            this.installMailhog(),
102
            {
103
                title: 'Install PHP-FPM',
104
                task: (ctx, task): Listr =>
105
                    task.newListr(
106
                        this.installPhpFpm(answers.phpVersions)
107
                    )
108
            },
109
            this.installDatabase(answers.database),
110
            this.installOptionalServices(answers),
111
            this.installTools(answers)
112
        ])
113
114
        try {
115
            // We're all set. Let's configure Jale.
116
            await tasks.run()
117
            console.log(`\n✨ Successfully installed Jale, Just Another Local Environment! ✅\n`)
118
        } catch (e) {
119
            console.error(e)
120
        }
121
    }
122
123
    /**
124
     * Configure Jale by parsing the answers and creating a configuration file.
125
     *
126
     * @param answers
127
     * @private
128
     */
129
    private configureJale = (answers: any): ListrTask => ({
130
        title: 'Configure Jale',
131
        task: (ctx, task): void => {
132
            let config = <Config>{
133
                domain: answers.domain,
134
                database: <Database>{password: 'root'},
135
                services: null // TODO: Make services configurable.
136
            }
137
138
            return fs.writeFileSync(jaleConfigPath, JSON.stringify(config, null, 2))
139
        }
140
    })
141
142
143
    //
144
    // Service installation functions
145
    //
146
147
    private installDnsMasq = (): ListrTask => ({
148
        title: 'Install Dnsmasq',
149
        task: (ctx, task): Listr =>
150
            task.newListr([
151
                {
152
                    title: 'Installing DnsMasq',
153
                    // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
154
                    skip: async (ctx): Promise<string | boolean> => {
155
                        const isInstalled = await client().packageManager.packageIsInstalled('dnsmasq')
156
157
                        if (isInstalled) return 'Dnsmasq is already installed.'
158
                    },
159
                    task: (new Dnsmasq).install
160
                },
161
                {
162
                    title: 'Configure DnsMasq',
163
                    task: (new Dnsmasq).configure
164
                },
165
                {
166
                    title: 'Restart DnsMasq',
167
                    task: (new Dnsmasq).restart
168
                }
169
            ])
170
    })
171
172
    private installNginx = (): ListrTask => ({
173
        title: 'Install Nginx',
174
        task: (ctx, task): Listr =>
175
            task.newListr([
176
                {
177
                    title: 'Installing Nginx',
178
                    // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
179
                    skip: async (ctx): Promise<string | boolean> => {
180
                        const isInstalled = await client().packageManager.packageIsInstalled('nginx')
181
182
                        if (isInstalled) return 'Nginx is already installed.'
183
                    },
184
                    task: (new Nginx).install
185
                },
186
                {
187
                    title: 'Configure Nginx',
188
                    task: (new Nginx).configure
189
                },
190
                {
191
                    title: 'Restart Nginx',
192
                    task: (new Nginx).restart
193
                }
194
            ])
195
    })
196
197
    private installPhpFpm = (phpVersions: string[]): ListrTask[] => {
198
        let phpInstallTasks: ListrTask[] = []
199
200
        phpVersions.forEach((phpVersion: string, index) => {
201
            phpInstallTasks.push({
202
                title: `Install ${phpVersion}`,
203
                task: (ctx, task): Listr =>
204
                    task.newListr([
205
                        {
206
                            title: `Installing ${phpVersion}`,
207
                            // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
208
                            skip: async (ctx): Promise<string | boolean> => {
209
                                if (phpVersion == '[email protected]') phpVersion = 'php'
210
                                const isInstalled = await client().packageManager.packageIsInstalled(phpVersion)
211
212
                                if (isInstalled) return `${phpVersion} is already installed.`
213
                            },
214
                            task: (getPhpFpmByName(phpVersion)).install
215
                        },
216
                        {
217
                            title: `Configure ${phpVersion}`,
218
                            task: (getPhpFpmByName(phpVersion)).configure
219
                        },
220
                        {
221
                            title: `Link ${phpVersion}`,
222
                            enabled: (): boolean => index === 0,
223
                            task: (getPhpFpmByName(phpVersion)).linkPhpVersion
224
                        },
225
                        {
226
                            title: `Restart ${phpVersion}`,
227
                            enabled: (): boolean => index === 0,
228
                            task: (getPhpFpmByName(phpVersion)).restart
229
                        },
230
                        {
231
                            title: `Stop ${phpVersion}`,
232
                            enabled: (): boolean => index !== 0,
233
                            task: (getPhpFpmByName(phpVersion)).stop
234
                        }
235
                    ])
236
            })
237
        })
238
239
        return phpInstallTasks
240
    }
241
242
    private installDatabase = (database: string): ListrTask => ({
243
        title: 'Install Database',
244
        task: (ctx, task): Listr =>
245
            task.newListr([
246
                {
247
                    title: `Installing ${database}`,
248
                    // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
249
                    skip: async (ctx): Promise<string | boolean> => {
250
                        const isInstalled = await client().packageManager.packageIsInstalled(database)
251
252
                        if (isInstalled) return `${database} is already installed.`
253
                    },
254
                    task: (getDatabaseByName(database)).install
255
                },
256
                {
257
                    title: 'Configure ${database}',
258
                    task: (getDatabaseByName(database)).configure
259
                },
260
                {
261
                    title: 'Restart ${database}',
262
                    task: (getDatabaseByName(database)).restart
263
                }
264
            ])
265
    })
266
267
    // TODO: make Mailhog configurable. Currently required due to php config which has mailhog set for sendmail.
268
    private installMailhog = (): ListrTask => ({
269
        title: 'Install Mailhog',
270
        task: (ctx, task): Listr =>
271
            task.newListr([
272
                {
273
                    title: `Installing Mailhog`,
274
                    // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
275
                    skip: async (ctx): Promise<string | boolean> => {
276
                        const isInstalled = await client().packageManager.packageIsInstalled('mailhog')
277
278
                        if (isInstalled) return `Mailhog is already installed.`
279
                    },
280
                    task: (new Mailhog).install
281
                },
282
                {
283
                    title: 'Configure Mailhog',
284
                    task: (new Mailhog).configure
285
                },
286
                {
287
                    title: 'Restart Mailhog',
288
                    task: (new Mailhog).restart
289
                }
290
            ])
291
    })
292
293
    private installOptionalServices = (answers: Answers): ListrTask => {
294
        let optionalServicesTasks: ListrTask[] = []
295
296
        answers.optionalServices.forEach((serviceName: string) => {
297
            const service = getOptionalServiceByname(serviceName)
298
            optionalServicesTasks.push({
299
                title: `Install ${service.service}`,
300
                task: (ctx, task): Listr =>
301
                    task.newListr([
302
                        {
303
                            title: `Installing ${service.service}`,
304
                            // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
305
                            skip: async (ctx): Promise<string | boolean> => {
306
                                const isInstalled = await client().packageManager.packageIsInstalled(service.service)
307
308
                                if (isInstalled) return `${service.service} is already installed.`
309
                            },
310
                            task: service.install
311
                        },
312
                        {
313
                            title: `Configure ${service.service}`,
314
                            task: service.configure
315
                        },
316
                        {
317
                            title: `Restart ${service.service}`,
318
                            task: service.restart
319
                        }
320
                    ])
321
            })
322
        })
323
324
        return {
325
            title: 'Install Optional Services',
326
            task: (ctx, task): Listr =>
327
                task.newListr(optionalServicesTasks)
328
        }
329
    }
330
331
    private installTools = (answers: Answers): ListrTask => {
332
        let toolsTasks: ListrTask[] = []
333
334
        answers.apps.forEach((toolName: string) => {
335
            const tool = getToolByName(toolName)
336
            toolsTasks.push({
337
                title: `Install ${tool.name}`,
338
                // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
339
                skip: async (ctx): Promise<string | boolean> => {
340
                    const isInstalled = await tool.isInstalled()
341
342
                    if (isInstalled) return `${tool.name} is already installed.`
343
                },
344
                task: tool.install
345
            })
346
        })
347
348
        return {
349
            title: 'Install Tools and Apps',
350
            task: (ctx, task): Listr =>
351
                task.newListr(
352
                    toolsTasks,
353
                    {concurrent: false}
354
                )
355
        }
356
    }
357
}
358
359
export default InstallController